﻿// $ PURE $ // class name and mean

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using DataSources.Holder;

namespace CodeTable
{
    public partial class CodeTable : UserControl
    {
        BlockRender blockRender = null;
        BlockInput blockInput = null;

        GridData gridData = null;
        GridView gridView = null;

        private IDataHolder dataSource = null;

        public IDataHolder DataSource
        {
            set
            {
                if (dataSource != value)
                {
                    dataSource = value;
                    OnDataSourceChanged();
                }
            }
            get
            {
                return dataSource;
            }
        }

        private bool useInstantSubmit = true;

        public CodeTable()
        {
            InitializeComponent();

            blockInput = new BlockInput("inputCodeBlock", patternCodeBlock);
            blockRender = new BlockRender("renderCodeBlock", patternCodeBlock, this.BackColor);
            this.Controls.Add(blockInput);
            this.Controls.Add(blockRender);

            // TODO: find more elegant way to do this things.
            blockInput.MouseWheel += new MouseEventHandler(BlockInput_MouseWheel);
            blockInput.KeyDown += new KeyEventHandler(BlockInput_KeyDown);
            blockInput.VisibleChanged += new EventHandler(BlockInput_VisibleChanged);
            //

            this.gridData = new GridData(patternCodeBlock.Margins);
            blockInput.TextChanged += new EventHandler(this.gridData.InputBlock_TextChanged);

            this.gridView = new GridView(this, blockRender, blockInput, bottomRightMarker);
            this.gridData.BlockChanged += new BlockEventHandler(this.gridView.GridData_BlockChanged);
            this.gridData.GridChanged += new EventHandler(this.gridView.GridData_GridChanged);
        }

        private void OnDataSourceChanged()
        {
            if (dataSource != null)
            {
                this.gridData.Initialize(dataSource);
                dataSource.EntryChanged += new EventHandler(this.DataSource_BlockChanged);
                //dataSource.TableChanged += new EventHandler(this.DataSource_TableChanged);
            }
            else
            {
                HideBlockInput();
                this.gridData.Initialize(dataSource);
            }
        }

        public Size TotalSize
        {
            get
            {
                return gridData.VisualAspect.TotalSize;
            }
        }

        public Point GetLocationByGridCoordinate(Point gridCoordinate)
        {
            return gridData.VisualAspect.GetLocationByGridCoordinate(gridCoordinate);
        }

        private void BlockInput_VisibleChanged(object sender, EventArgs e)
        {
            if (!useInstantSubmit)
                return;

            if (blockInput.CurrentGridCoordinate.X == 0 || blockInput.CurrentGridCoordinate.Y == 0)
                return;

            string hash = DataSources.Utils.GridCoordinate2Hash(blockInput.CurrentGridCoordinate);
            if (blockInput.Visible)
            {
                if (blockInput.CurrentGridCoordinate.Y < dataSource.GetSize().Height)
                {
                    dataSource.Request(hash);
                }
            }
            else
            {
                dataSource.Submit(hash, blockInput.Text);
                // optimization - to don't redraw view control under input control
                if (blockInput.needRefreshImage)
                {
                    gridView.GridData_BlockChanged(
                        null, 
                        new BlockEventArgs(blockInput.CurrentGridCoordinate, blockInput.Text, blockInput.ClientSize)
                    );
                    blockInput.needRefreshImage = false;
                }
            }
        }

        private void BlockInput_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
        {            
            if (e.Delta != 0)
            {
                this.AutoScrollPosition = new Point(-this.AutoScrollPosition.X, -(this.AutoScrollPosition.Y + e.Delta));
            }
        }

        private void HideBlockInput()
        {
            blockInput.Visible = false;
            blockInput.Text = string.Empty;
            blockInput.CurrentGridCoordinate = Point.Empty;
        }

        private void BlockInput_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                HideBlockInput();
                this.Focus();
            }
        }

        public void CodeBlockView_MouseClick(object sender, MouseEventArgs e) // public is a HACK
        {
            GridView.Block block = sender as GridView.Block;
            if (block == null)
                return;
            SetGridCellEditor(block.GridCoordinate);
        }
        
        private void SetGridCellEditor(Point gridCoordinates)
        {
            if (blockInput.Visible && blockInput.CurrentGridCoordinate == gridCoordinates)
                return;
            if (blockInput.Visible)
            {
                HideBlockInput();
            }
            GridData.Block codeBlock = gridData.GetBlock(gridCoordinates);
            if (codeBlock == null)
            {
                codeBlock = gridData.AddBlock(gridCoordinates);
            }
            Point location = gridData.VisualAspect.GetLocationByGridCoordinate(gridCoordinates);
            location.Offset(this.AutoScrollPosition);
            blockInput.CurrentGridCoordinate = gridCoordinates;
            blockInput.Location = location;
            blockInput.Text = codeBlock.Text;
            blockInput.Visible = true;
            blockInput.Focus();
        }

        private void CodeTable_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point gridCoordinate = gridData.VisualAspect.GetGridCoordinateByLocation(new Point(e.X - this.AutoScrollPosition.X, e.Y - this.AutoScrollPosition.Y));
                if (gridCoordinate.X > -1 && gridCoordinate.Y > -1)
                {
                    SetGridCellEditor(gridCoordinate);
                }
            }
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            // Calling the base class OnPaint
            base.OnPaint(pe);

            if (dataSource == null)
                return;

            // TODO: make special struct
            float columnDelimiterWidth = gridData.VisualAspect.ColumnDelimiterWidth;
            float rowDelimiterWidth = gridData.VisualAspect.RowDelimiterWidth;
            int[] codeBlockGridWidths = gridData.VisualAspect.ColumnWidths;
            int[] codeBlockGridHeights = gridData.VisualAspect.RowHeights;
            int columnCount = gridData.MaxColumns;
            int rowCount = gridData.MaxRows;

            // Table Drawing
            Point clientRectangleTopLeft = new Point(
                -this.AutoScrollPosition.X, 
                -this.AutoScrollPosition.Y
            );
            Point clientRectangleBottomRight = new Point(
                clientRectangleTopLeft.X + this.ClientRectangle.Width, 
                clientRectangleTopLeft.Y + this.ClientRectangle.Height
            );
            Pen pen = new Pen(Color.Black, columnDelimiterWidth);
            int clientWordX = 0;
            for (int column = 0; column <= columnCount; ++column)
            {
                if (clientWordX >= clientRectangleTopLeft.X && clientWordX <= clientRectangleBottomRight.X)
                {
                    pe.Graphics.DrawLine(pen, (float)clientWordX - clientRectangleTopLeft.X, 0.0f, (float)clientWordX - clientRectangleTopLeft.X, (float)this.ClientRectangle.Height);
                }
                if (column < columnCount)
                {
                    clientWordX += codeBlockGridWidths[column] + (int)columnDelimiterWidth;
                }
            }

            pen.Width = rowDelimiterWidth;
            pen.Color = Color.Gray;
            int clientWordY = 0;
            for (int row = 0; row <= rowCount; ++row)
            {
                if (clientWordY >= clientRectangleTopLeft.Y && clientWordY <= clientRectangleBottomRight.Y)
                {
                    pe.Graphics.DrawLine(pen, 0.0f, (float)clientWordY - clientRectangleTopLeft.Y, (float)this.ClientRectangle.Width, (float)clientWordY - clientRectangleTopLeft.Y);
                }
                if (row < rowCount)
                {
                    clientWordY += codeBlockGridHeights[row] + (int)rowDelimiterWidth;
                }
            }
        }

        private void DataSource_TableChanged(object sender, EventArgs e) // TODO: use it somehow
        {
            this.gridData.Initialize(dataSource);
        }

        private void DataSource_BlockChanged(object sender, EventArgs e)
        {
            DataSources.BlockRequestEventArgs bre = e as DataSources.BlockRequestEventArgs;
            Point gridCoordinates = DataSources.Utils.Hash2GridCoordinate(bre.hash);

            GridData.Block codeBlock = this.gridData.GetBlock(gridCoordinates);
            if (codeBlock == null)
            {
                codeBlock = gridData.AddBlock(gridCoordinates);
            }
            codeBlock.Text = bre.text;

            if (blockInput.Visible && blockInput.CurrentGridCoordinate == gridCoordinates)
            {
                blockInput.Text = bre.text;
            }
        }        

        private void reloadEntireTableToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //dataSource.ReloadAll(); // TODO: restore this code
        }

        private void submitEntireTableToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //dataSource.SubmitAll(gridData.GetBlocks()); // TODO: restore this code
        }

        private void reloadCurrentBlockToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.Assert(blockInput.Visible);
            string hash = DataSources.Utils.GridCoordinate2Hash(blockInput.CurrentGridCoordinate);
            dataSource.Request(hash);
        }

        private void submitCurrentBlockToolStripMenuItem_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.Assert(blockInput.Visible);
            string hash = DataSources.Utils.GridCoordinate2Hash(blockInput.CurrentGridCoordinate);
            string text = gridData.GetBlock(blockInput.CurrentGridCoordinate).Text;
            dataSource.Submit(hash, text);
        }

        private void contextMenuStrip1_Opened(object sender, EventArgs e)
        {
            if (blockInput.Visible)
            {
                debugStuffToolStripMenuItem1.Enabled = true;
                submitCurrentBlockToolStripMenuItem.Enabled = true;
            }
            else
            {
                debugStuffToolStripMenuItem1.Enabled = false;
                submitCurrentBlockToolStripMenuItem.Enabled = false;
            }
        }

        private void debugStuffToolStripMenuItem2_CheckedChanged(object sender, EventArgs e)
        {
            useInstantSubmit = !debugStuffToolStripMenuItem2.Checked;
        }
    }
}